#include "OP_CreateCone.h"
#include "../Geometries/PolygonMesh.h"

using namespace std;
using namespace CICore;
using namespace CIGeom;
using namespace CIView;

///<************************************************************************************************************>
///<************************************* region CONSTANTS DEFINITION ******************************************>
///<************************************************************************************************************>

/***************************************************************************************************************
************************************** endregion CONSTANTS DEFINITION ******************************************
****************************************************************************************************************/












/// <summary> 
/// Constructor 
/// </summary>
/// <param name="v"> A reference to the vector that contains the four 3D views pointers </param>
CIOps::OP_CreateCone::OP_CreateCone (vector<CIView::View*>& v)
: Operation (v), cone (0), radius (0.002f), height (0.004f), coneDetail (1), rotation_x (0) {

    newSelectionMode = scene.GetSelectionMode();

    stacks = OperationManager::Stacks;
    slices = OperationManager::Slices;
}

/// <summary> 
/// Destructor 
/// </summary>
CIOps::OP_CreateCone::~OP_CreateCone() {
    if (status != COMPLETED)
        delete cone;
}










///<************************************************************************************************************>
///<**************************************** region Utility Methods ********************************************>
///<************************************************************************************************************>

/// <summary> 
/// Method called to execute directly the operation 
/// </summary>
/// <returns> The operation status </returns>
int CIOps::OP_CreateCone::Exec() {
    switch (status) {
        case INITIAL:
            status = WAITING_FOR_EVENTS;
            break;
    }
    return status;        
}

/// <summary> 
/// Method called to undo the operation 
/// </summary>
void CIOps::OP_CreateCone::Undo() {
    status = UNDO;      

    scene.RemoveGeometry (cone->GetName().c_str());     //Removes the geometry from the 3D scene
    RestoreOldSelection();                              //Restores the old selection status
    UpdateViews();                                      //Updates the 3D views
}

/// <summary> 
/// Method called to redo the operation 
/// </summary>
void CIOps::OP_CreateCone::Redo() {
    status = COMPLETED;    

    scene.AddGeometry (cone);                       //Re-adds the geometry in the 3D scene  
    RestoreNewSelection();                          //Restore the new selectio status
    UpdateViews();                                  //Updates the 3D views
}

/// <summary> 
/// Mouse Pressed event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse press event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Pressed event </param>
void CIOps::OP_CreateCone::MousePressEvent (CIView::View* view, QMouseEvent* e) {
    float x = e->x();
    float y = e->y();
    
    SaveOldSelection();                             //Saves the old selection status
    scene.DeselectAllFaces();                       //Deselects all faces       //TODO -> Deselect all (included geometries and groups)

    view->WindowToWorldCoordinates (&x, &y);        //Unproject the mouse cursor coordinates

    Orientation& cameraOrient = view->GetCameraOrientation();

    //Computing the cone topLeft according to the current camera of the view that has thrown the event
    switch (view->GetCameraType()) {
        case Camera::TOP:
            topLeft.x = x;
            topLeft.z = y;
            rotation_x = 90.0f;
            break;
        
        case Camera::FRONT:
            topLeft.x = x;
            topLeft.y = -y;
            break;
        
        case Camera::RIGHT:            
            topLeft.y = -y;
            topLeft.z = -x;
            break;
    }

    unsigned int id = scene.GenerateNewGeometryID();        //Gets a reference to the number of geometries that have been added to the scene
    stringstream name;
    name << "Cone" << id;                                   //Sets the name of the new triangle mesh 

    cone = new PolygonMesh (name.str());                    //Creates the cone 

    CreateConeWithTriangles (stacks*coneDetail, 
                                 slices*coneDetail, 
                                 height, 
                                 radius);           //Creates the cone with triangles as faces

    scene.AddGeometry (cone);                               //Adds the cone in the 3D scene

    UpdateViews();                                          //Updates the 3D views
}

/// <summary> 
/// Mouse Released event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse release event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Released event </param>
void CIOps::OP_CreateCone::MouseReleaseEvent (CIView::View* view, QMouseEvent* e) {
    status = COMPLETED;
    
    view->SetPrimitivesSelected (true);                     //Signals that there is a primitive selected

    SaveNewSelection();                     //Saves the new selection status
    cone->UpdateBoundingBox();              //Updates the cone bounding box
}

/// <summary> 
/// Mouse Moved event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse move event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Moved event </param>
void CIOps::OP_CreateCone::MouseMoveEvent (CIView::View* view, QMouseEvent* e) {
    float x = e->x();
    float y = e->y();

    view->WindowToWorldCoordinates (&x, &y);            //Unproject the mouse cursor coordinates

    Orientation& cameraOrient = view->GetCameraOrientation();

    Vector3D drag;    
    float newRadius, scaleRadius; 
    float newHeight, scaleHeight;
    Matrix scaling;

    //Computing the new radius value according to the mouse drag values
    switch (view->GetCameraType()) {
        case Camera::TOP:
            drag.x = x - topLeft.x;
            drag.z = y - topLeft.z;
            newRadius = drag.x ? drag.x / 2.0f : radius;
            newHeight = drag.z ? drag.z : height;

            scaleRadius = newRadius / radius;
            scaleHeight = newHeight / height;

            scaling.SetScaling (scaleRadius, scaleRadius, scaleHeight);            
            break;
        
        case Camera::FRONT:
            drag.x = x - topLeft.x;
            drag.y = -topLeft.y - y;
            newRadius = drag.x ? drag.x / 2.0f : radius;
            newHeight = drag.y ? drag.y : height;

            scaleRadius = newRadius / radius;
            scaleHeight = newHeight / height;

            scaling.SetScaling (scaleRadius, scaleHeight, scaleRadius);         
            break;
        
        case Camera::RIGHT:         
            drag.z = -topLeft.z - x;
            drag.y = -topLeft.y - y;            
            newRadius = drag.z ? drag.z / 2.0f : radius;
            newHeight = drag.y ? drag.y : height;

            scaleRadius = newRadius / radius;
            scaleHeight = newHeight / height;

            scaling.SetScaling (scaleRadius, scaleHeight, scaleRadius);         
            break;
    }    

    height = newHeight;
    radius = newRadius;

    //Scaling the cone according to the new radius and height
    Matrix m1, m2;
    m1.SetTranslation (-topLeft.x, -topLeft.y, -topLeft.z);
    m2.SetTranslation (topLeft.x, topLeft.y, topLeft.z);
    
    Matrix m;
    m = m1*scaling*m2;
    cone->ApplyMatrix (m);

    UpdateViews();                                      //Updates the 3D views
}

/// <summary> 
/// Wheel event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse wheel event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Wheel event </param>
void CIOps::OP_CreateCone::WheelEvent (CIView::View* view, QWheelEvent* e) {
    if ((e->delta() > 0 && coneDetail == 4) || (e->delta() <= 1 && coneDetail == 1))
        return;

    e->delta() > 0 ? coneDetail++ : coneDetail--;       //Increases/Decreases the cone triangle tessellation detail
    
    if (stacks*coneDetail > 30 || slices*coneDetail > 30)
        return;

    cone->DeleteAllVertices();                          //Deletes the old cone vertices

    CreateConeWithTriangles (stacks*coneDetail, 
                                 slices*coneDetail, 
                                 height, 
                                 radius);    //Creates the cone with triangles as faces

    UpdateViews();                                      //Updates the 3D views
}
/***************************************************************************************************************
***************************************** endregion Utility Methods ********************************************
****************************************************************************************************************/









///<***********************************************************************************************************>
///<*********************************** region Triangles Tessellation *****************************************>
///<***********************************************************************************************************>

/// <summary> 
/// Method called to create a cone centered at the origin 
/// </summary>
/// <param name="stacks"> The number of stacks (for the cone subdivision in faces) </param>
/// <param name="slices"> The number of slices (for the cone subdivision in faces) </param>
/// <param name="height"> The cone height </param>
/// <param name="radius"> The cone radius </param>
void CIOps::OP_CreateCone::CreateConeWithTriangles (int stacks, int slices, float height, float radius) {
    double stackHeight = height / stacks;
    double sliceAngle = 2.0 * PI / slices;
    float radiusDecrement = radius / stacks;

    int i, j, v1_index, v2_index, v3_index;

    float y0 = 0.5 * height; 
    float y1 = y0 - stackHeight;

    double a = 0.0;                                 //The angle     
    float radiusA = radius;
    float radiusB = radius - radiusDecrement;
    float x = radiusA * cos(0.0);                  
    float z = radiusA * sin(0.0);    

    v1_index = cone->AddVertex (x, y0, z);

    x = radiusB * cos(0.0);                  
    z = radiusB * sin(0.0);     
                                                
    v2_index = cone->AddVertex (x, y1, z);   

    for (i=0; i<stacks; i++) {
        y0 = 0.5 * height - i * stackHeight;
        y1 = y0 - stackHeight;
        
        j = i ? 0 : 1;

        for (; j<=slices; j++) {
            a = j * sliceAngle;
            x = radiusA * cos(a);
            z = radiusA * sin(a);

            //Adding a face
            v3_index = cone->AddVertex (x, y0, z);  
            AddFace (v1_index, v2_index, v3_index);
            v1_index = v3_index;
            
            x = radiusB * cos(a);
            z = radiusB * sin(a);

            //Adding another one 
            v3_index = cone->AddVertex (x, y1, z);  
            AddFace (v1_index, v2_index, v3_index);
            v2_index = v3_index;
        }
        radiusA -= radiusDecrement;
        radiusB -= radiusDecrement;
    }

    double y = height / 2.0f;
    
    //Center point    
    v1_index = cone->AddVertex (0, y, 0); 

    //Drawing the top cap
    a = 0.0;
    x = radius * cos(a);
    z = radius * sin(a);
    v2_index = cone->AddVertex (x, y, z); 
    a += sliceAngle;

    for (j=1; j<=slices; j++) {
        x = radius * cos(a);
        z = radius * sin(a);

        //Adding a face
        v3_index = cone->AddVertex (x, y, z);  
        AddFace (v1_index, v2_index, v3_index);
        v2_index = v3_index;

        a += sliceAngle;
    }

    //Translating the cone vertices according to the position desidered
    Point coneCentre;                

    if (rotation_x == 0) {
         coneCentre.x = topLeft.x+radius;
         coneCentre.y = topLeft.y+height/2.0f;
         coneCentre.z = topLeft.z+radius;
    }
    else {
         coneCentre.x = topLeft.x+radius;
         coneCentre.y = topLeft.y+radius;
         coneCentre.z = topLeft.z+height/2.0f;
    }
    Matrix m1, m2, m;
    m1.SetRotation (rotation_x, 0, 0);
    m2.SetTranslation (coneCentre.x, coneCentre.y, coneCentre.z);
    m = m1 * m2;
    cone->ApplyMatrix (m);
}

/// <summary> 
/// Method called to add a face (triangle) to the cone
/// </summary>
/// <param name="v1"> The index of the first vertex int the cone vertex library </param>
/// <param name="v2"> The index of the second vertex int the cone vertex library </param>
/// <param name="v3"> The index of the third vertex int the cone vertex library </param>
void CIOps::OP_CreateCone::AddFace (int v1, int v2, int v3) {
    if (v1 == v2 || v1 == v3 || v2 == v3)
        return;

    VertexLib vertices(3);
    VertexLib& coneVertices = cone->GetVertices();
    Face* newFace;

    if (rotation_x == 0) {
        vertices[0] = coneVertices[v1];
        vertices[1] = coneVertices[v2];
        vertices[2] = coneVertices[v3];
    }
    else {
        vertices[0] = coneVertices[v2];
        vertices[1] = coneVertices[v1];
        vertices[2] = coneVertices[v3];
    }


    newFace = new Face (vertices);
    newFace->ComputeNormal();
    cone->AddFace (newFace);
}
/***************************************************************************************************************
*********************************** endregion Triangles Tessellation *******************************************
****************************************************************************************************************/
